package com.joysuch.wwyt.screen.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.joysuch.wwyt.config.RedisClient;
import com.joysuch.wwyt.core.common.util.MapUtils;
import com.joysuch.wwyt.core.entity.BaseConfig;
import com.joysuch.wwyt.core.enums.DeleteFlag;
import com.joysuch.wwyt.core.enums.Status;
import com.joysuch.wwyt.core.exception.ApiBusinessException;
import com.joysuch.wwyt.core.service.BaseConfigService;
import com.joysuch.wwyt.core.support.Context;
import com.joysuch.wwyt.screen.dto.ScreenMapFactoryConfigDTO;
import com.joysuch.wwyt.screen.dto.ScreenMapFactoryPointDTO;
import com.joysuch.wwyt.screen.dto.ScreenMapFactoryPointsDTO;
import com.joysuch.wwyt.screen.entity.ScreenMapFactoryConfig;
import com.joysuch.wwyt.screen.mapper.ScreenMapFactoryConfigMapper;
import com.joysuch.wwyt.screen.service.ScreenMapFactoryConfigService;
import com.joysuch.wwyt.screen.vo.ScreenMapFactoryConfigVO;
import com.joysuch.wwyt.util.RegionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author ZhaoQiuWei
 * @Date 2023/5/6 10:35
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class ScreenMapFactoryConfigServiceImpl extends ServiceImpl<ScreenMapFactoryConfigMapper, ScreenMapFactoryConfig> implements ScreenMapFactoryConfigService {


    @Autowired
    private RedisClient redisClient;

    @Autowired
    private BaseConfigService baseConfigService;

    private static final String FILE_NAME = "mapConfig.json";

    private static final String REDIS_KEY = "factoryMapConfig";

    private static final String FACTORY_BASE_CONFIG_CODE = "wwyt.factory.map";

    @Override
    public Long addOrUpdate(ScreenMapFactoryConfigDTO dto) throws ApiBusinessException {
        this.validateBeforeSave(dto.getId(), dto.getBuildingId(), dto.getFactoryName());
        //产品要求服务器没有buildingId命名的文件则报错,文件点位信息错误可以正常保存
        //根据建筑id获取服务器配置的点位信息
        String fileDataStr = this.getFileDataStr(dto.getBuildingId());
        ScreenMapFactoryConfig config;
        Integer sort = dto.getSort();
        if (dto.getId() != null) {
            config = this.getById(dto.getId());
        } else {
            config = new ScreenMapFactoryConfig();
            Integer maxSort = this.getMaxSort();
            sort = maxSort == null ? 1 : maxSort + 1;
        }
        BeanUtils.copyProperties(dto, config);
        try {
            MapUtils.SafetyPoints mapPoints = this.convertFileDataStr(fileDataStr, dto.getBuildingId());
            if (mapPoints != null) {
                config.setLocation(JSONUtil.toJsonStr(mapPoints));
            }
        } catch (Exception e) {
            log.error("地图多分厂设置错误==========", e);
        }
        config.setSort(sort);
        this.saveOrUpdate(config);
        //更新redis数据
        List<ScreenMapFactoryConfigVO> dbList = this.getListFromDb(Context.getCompanyCode(), Context.getCurrentTenantId());
        this.saveRedisFactoryConfig(dbList);
        return config.getId();
    }

    private Integer getMaxSort() {
        return this.baseMapper.getMaxSort(Context.getCompanyCode(), Context.getCurrentTenantId());
    }


    @Override
    public List<ScreenMapFactoryConfigVO> getList(String orgCode, Long tenentId) {
        //先从redis获取 没有再查数据库
        List<ScreenMapFactoryConfigVO> configs = this.getRedisConfigList(orgCode, tenentId);
        if (CollectionUtil.isEmpty(configs)) {
            //查询数据库并将数据存入redis
            configs = this.getListFromDb(orgCode, tenentId);
            if (CollectionUtil.isNotEmpty(configs)) {
                this.saveRedisFactoryConfig(configs);
            }
        }
        return configs;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        ScreenMapFactoryConfig config = this.getById(id);
        if (config != null) {
            config.setDeleteFlag(DeleteFlag.DELETED.getCode());
            this.updateById(config);
            //更新redis数据
            List<ScreenMapFactoryConfigVO> dbList = this.getListFromDb(Context.getCompanyCode(), Context.getCurrentTenantId());
            this.saveRedisFactoryConfig(dbList);
        }
    }

    @Override
    public String getBuildingIdByLocation(ScreenMapFactoryPointsDTO dto) {
        List<ScreenMapFactoryConfigVO> configList = this.getList(dto.getOrgCode(), dto.getTenantId());
        if (CollectionUtil.isNotEmpty(configList) && StringUtils.isNotBlank(dto.getPoints())) {
            for (ScreenMapFactoryConfigVO config : configList) {
                if (config.getLocation() == null) {
                    continue;
                }
                MapUtils.SafetyPoints configPoints = config.getLocation();
                Double[] pointLons = new Double[configPoints.getPoints().size()];
                Double[] pointLats = new Double[configPoints.getPoints().size()];
                MapUtils.getCoordinateArr(configPoints.getPoints(), pointLons, pointLats);
                List<ScreenMapFactoryPointDTO> points = JSONUtil.toList(dto.getPoints(), ScreenMapFactoryPointDTO.class);
                for (ScreenMapFactoryPointDTO point : points) {
                    if (RegionUtil.isInPolygon(point.getX(), point.getY(), pointLons, pointLats)) {
                        return config.getBuildingId();
                    }
                }
            }
        }
        return "";
    }

    @Override
    public MapUtils.SafetyPoints getMapPoints(String buildingId) throws Exception {
        //读取文件点位信息
        String fileDataStr = this.getFileDataStr(buildingId);
        return this.convertFileDataStr(fileDataStr, buildingId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void editOrder(Long upId, Long downId) {
        ScreenMapFactoryConfig upOne = this.getById(upId);
        ScreenMapFactoryConfig downOne = this.getById(downId);
        Integer sort = upOne.getSort();
        upOne.setSort(downOne.getSort());
        downOne.setSort(sort);
        this.updateById(upOne);
        this.updateById(downOne);
        //更新redis数据
        List<ScreenMapFactoryConfigVO> dbList = this.getListFromDb(Context.getCompanyCode(), Context.getCurrentTenantId());
        this.saveRedisFactoryConfig(dbList);
    }

    private MapUtils.SafetyPoints convertFileDataStr(String fileDataStr, String buildingId) {
        //解析
        if (StringUtils.isBlank(fileDataStr)) {
            throw new IllegalArgumentException("建筑id[" + buildingId + "]地图文件信息获取失败");
        }
        Object floorsObj = JSONUtil.parseObj(fileDataStr).get("floors");
        if (floorsObj == null) {
            throw new IllegalArgumentException("建筑id[" + buildingId + "]地图文件信息获取失败");
        }
        Object f1Obj = JSONUtil.parseObj(floorsObj.toString()).get("F1");
        if (f1Obj == null) {
            throw new IllegalArgumentException("建筑id[" + buildingId + "]地图文件信息获取失败");
        }
        Object leftBottomObj = JSONUtil.parseObj(f1Obj.toString()).get("leftBottom");
        Object rightTopObj = JSONUtil.parseObj(f1Obj.toString()).get("rightTop");
        if (leftBottomObj == null || rightTopObj == null) {
            throw new IllegalArgumentException("建筑id[" + buildingId + "]地图文件信息获取失败");
        }
        MapUtils.SafetyPoints safetyPoints = new MapUtils.SafetyPoints();
        List<MapUtils.SafetyPoint> points = new ArrayList<>();
        //左下角点位
        MapUtils.SafetyPoint leftBottom = JSONUtil.toBean(leftBottomObj.toString(), MapUtils.SafetyPoint.class);
        //右上角点位
        MapUtils.SafetyPoint rightTop = JSONUtil.toBean(rightTopObj.toString(), MapUtils.SafetyPoint.class);
        //左上角点位
        MapUtils.SafetyPoint leftTop = new MapUtils.SafetyPoint();
        leftTop.setX(leftBottom.getX());
        leftTop.setY(rightTop.getY());
        //右下角点位
        MapUtils.SafetyPoint rightBottom = new MapUtils.SafetyPoint();
        rightBottom.setX(rightTop.getX());
        rightBottom.setY(leftBottom.getY());
        points.add(leftBottom);
        points.add(leftTop);
        points.add(rightTop);
        points.add(rightBottom);
        safetyPoints.setPoints(points);
        return safetyPoints;
    }

    private String getFileDataStr(String buildingId) throws ApiBusinessException {
        BaseConfig config = baseConfigService.findConfig(FACTORY_BASE_CONFIG_CODE);
        if (config == null || StringUtils.isBlank(config.getValue())) {
            throw new ApiBusinessException(Status.FACTORY_BASE_CONFIG_NON_EXISTENT);
        }
        String fPath = config.getValue() + File.separator + buildingId + File.separator + FILE_NAME;
        String fileDataStr = null;
        try {
            byte[] bytes = Files.readAllBytes(Paths.get(fPath));
            fileDataStr = new String(bytes, StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }

        if (StringUtils.isBlank(fileDataStr)) {
            throw new ApiBusinessException(Status.FACTORY_CONFIG_BUILDINGID_OR_MAPCONFIG_ERROR);
        }
        return fileDataStr;
    }

    private void validateBeforeSave(Long id, String buildingId, String factoryName) throws ApiBusinessException {
        ScreenMapFactoryConfig buildingIdConfig = this.getOne(new LambdaQueryWrapper<ScreenMapFactoryConfig>().eq(ScreenMapFactoryConfig::getBuildingId, buildingId)
                .ne(id != null, ScreenMapFactoryConfig::getId, id)
                .eq(ScreenMapFactoryConfig::getTenentId, Context.getCurrentTenantId())
                .likeRight(ScreenMapFactoryConfig::getOrgCode, Context.getCompanyCode())
                .eq(ScreenMapFactoryConfig::getDeleteFlag, DeleteFlag.EFFECTIVE.getCode())
        );

        ScreenMapFactoryConfig nameConfig = this.getOne(new LambdaQueryWrapper<ScreenMapFactoryConfig>().eq(ScreenMapFactoryConfig::getFactoryName, factoryName)
                .ne(id != null, ScreenMapFactoryConfig::getId, id)
                .eq(ScreenMapFactoryConfig::getTenentId, Context.getCurrentTenantId())
                .likeRight(ScreenMapFactoryConfig::getOrgCode, Context.getCompanyCode())
                .eq(ScreenMapFactoryConfig::getDeleteFlag, DeleteFlag.EFFECTIVE.getCode())
        );
        if (buildingIdConfig != null) {
            throw new ApiBusinessException(Status.FACTORY_CONFIG_BUILDINGID_EXIST);
        }
        if (nameConfig != null) {
            throw new ApiBusinessException(Status.FACTORY_CONFIG_NAME_EXIST);
        }
    }

    private String getRedisKey(Long tenentId, String orgCode) {
        return REDIS_KEY + ":" + tenentId + ":" + orgCode;
    }

    private void saveRedisFactoryConfig(List<ScreenMapFactoryConfigVO> list) {
        if (CollectionUtil.isNotEmpty(list)) {
            redisClient.set(this.getRedisKey(Context.getCurrentTenantId(), Context.getCompanyCode()), JSONUtil.toJsonStr(list));
        } else {
            redisClient.removeKey(this.getRedisKey(Context.getCurrentTenantId(), Context.getCompanyCode()));
        }
    }

    private List<ScreenMapFactoryConfigVO> getRedisConfigList(String orgCode, Long tenentId) {
        String dataStr = redisClient.get(this.getRedisKey(tenentId, orgCode));
        if (StringUtils.isNotBlank(dataStr)) {
            return JSONUtil.toList(dataStr, ScreenMapFactoryConfigVO.class);
        }
        return Lists.newArrayList();
    }

    private List<ScreenMapFactoryConfigVO> getListFromDb(String orgCode, Long tenentId) {
        List<ScreenMapFactoryConfigVO> configs = new ArrayList<>();
        List<ScreenMapFactoryConfig> datas = this.list(new LambdaQueryWrapper<ScreenMapFactoryConfig>()
                .eq(ScreenMapFactoryConfig::getTenentId, tenentId)
                .likeRight(ScreenMapFactoryConfig::getOrgCode, orgCode)
                .eq(ScreenMapFactoryConfig::getDeleteFlag, DeleteFlag.EFFECTIVE.getCode())
                .orderByAsc(ScreenMapFactoryConfig::getSort));
        if (CollectionUtil.isNotEmpty(datas)) {
            for (ScreenMapFactoryConfig d : datas) {
                ScreenMapFactoryConfigVO vo = new ScreenMapFactoryConfigVO();
                BeanUtils.copyProperties(d, vo);
                if (StringUtils.isNotBlank(d.getLocation())) {
                    vo.setLocation(MapUtils.parse3DSafetyPointsFromJson(d.getLocation()));
                }
                configs.add(vo);
            }
        }
        return configs;
    }
}
